﻿unit CircularBuffer;

interface

uses
  Winapi.Windows, System.SysUtils, CriticalSection;
  
type

  TCircularBuffer = class
  public
    m_pBuffer : PByte;
  private
    m_nOldBufSize: Integer;            (* 环包原长度      *)
    m_nBufSize: Integer;               (* 环形Buffer长度  *)
    m_HeadPos: Integer;                (* 头指针位置(读)  *)
    m_TailPos: Integer;                (* 尾指针位置(写)  *)
    m_bThreadSafe: Boolean;            (* 线程安全标志    *)
    m_CirBufferLock: TCriticalSection; (* 安全锁          *)
  published
    property FThreadSafe: Boolean read m_bThreadSafe write m_bThreadSafe;
  private
    (* 临界加锁                             *)
    procedure Lock;
    (* 临界解锁                             *)
    procedure UnLock;
    (* 得到有效缓冲区大小(已占用)           *)
    function GetValidCount: Integer;
    (* 要写入的长度是否溢出环形Buffer       *)
    function IsOverFlowBuffer(nLen: Integer): Boolean;
    (* 索引是否溢出需要从0开始写数据        *)
    function IsIndexOverFlow(nLen: Integer): Boolean;
    (* Buffer环形缓冲区大小收缩             *)
    procedure BufferResize;

  public
    (* 重置Buffer                           *)
    procedure SetEmpty;
    (* 移动m_HeadPos位置                    *)
    function HeadSafePosInc(nStep: Integer): Boolean;
    (* 得到有效缓冲区大小(已占用)           *)
    function GetSafeValidCount: Integer;
    (* 向环形Buffer写入数据                 *)
    procedure WriteCirBuffer(pBuffer: PChar; m_nBufLen: Integer);
    (* 仅仅读出数据，并不移动环形Buffer指针 *)
    procedure ReadCirBuffer(pBuffer: PChar; m_nBufLen: Integer); overload;
    procedure ReadCirBuffer(pBuffer: PChar; m_nBufLen: Integer; nPos: Integer); overload;
    (* 初始化                               *)
    procedure InitCirBuffer;
  public
    constructor Create(nSize: Integer = 4096);
    destructor Destroy; override;
  end;


implementation

constructor TCircularBuffer.Create(nSize: Integer);
begin
  inherited Create;
  m_bThreadSafe := True;
  m_nBufSize := nSize;
  m_nOldBufSize := nSize;
  m_pBuffer := nil;
  GetMem(m_pBuffer, m_nBufSize);
  ZeroMemory(m_pBuffer, m_nBufSize);
  m_HeadPos := 0;
  m_TailPos := 0;
  m_CirBufferLock := TCriticalSection.Create;
  m_CirBufferLock.SetLockName('m_CirBufferLock');
end;

destructor TCircularBuffer.Destroy;
begin
  FreeMem(m_pBuffer, m_nBufSize);
  FreeAndNil(m_CirBufferLock);
  inherited Destroy;
end;

procedure TCircularBuffer.InitCirBuffer;          
begin
  if (m_nOldBufSize <> m_nBufSize) then
  begin
    FreeMem(m_pBuffer, m_nBufSize);
    GetMem(m_pBuffer, m_nOldBufSize);
    ZeroMemory(m_pBuffer, m_nOldBufSize);
    m_nBufSize := m_nOldBufSize;
  end;
  m_HeadPos := 0;
  m_TailPos := 0;
end;

procedure TCircularBuffer.Lock;
begin
  if m_bThreadSafe then
    m_CirBufferLock.Lock;
end;

procedure TCircularBuffer.UnLock;
begin
  if m_bThreadSafe then
    m_CirBufferLock.UnLock;
end;

function TCircularBuffer.GetValidCount: Integer;
var
  m_ValidCount: Integer;
begin
  (* 尾部-头部 *)
  m_ValidCount := m_TailPos - m_HeadPos;
  if (m_ValidCount < 0) then
    m_ValidCount := m_nBufSize + m_ValidCount;
  Result := m_ValidCount;
end;

function TCircularBuffer.IsOverFlowBuffer(nLen: Integer): Boolean;
begin
  Result := nLen >= m_nBufSize - GetValidCount();
end;

function TCircularBuffer.IsIndexOverFlow(nLen: Integer): Boolean;
begin
  Result := nLen + m_TailPos >= m_nBufSize;
end;

procedure TCircularBuffer.BufferResize;
var
  m_pNewBuffer: PByte;
  m_nPrevBufSize: Integer;
begin
  (* 得到原环形Buffer大小                 *)
  m_nPrevBufSize := m_nBufSize;
  (* 分配2倍大小缓冲                      *)
  m_nBufSize := m_nBufSize shl 1;
  GetMem(m_pNewBuffer, m_nBufSize);
  ZeroMemory(m_pNewBuffer, m_nBufSize);
  (* 拷贝原数据到新分配的缓冲区位置       *)
  CopyMemory(Pointer(LongInt(m_pNewBuffer)), Pointer(LongInt(m_pBuffer)), m_nPrevBufSize);

  (* --------------------------  说明  ----------------------------*)
  (* 当尾(pos)<头(pos) 例如：                                      *)
  (*  0  1  2  3  4  5  6  7  8  9                                 *)
  (*        尾    头                有效数据为: 4-5-6-7-8-9-0-1-2  *)
  (* 新拓展的包为：                                                *)
  (*  0  1  2  3  4  5  6  7  8  9  10 11  12 13 14 15 16 17 18 19 *)
  (*              头                       尾                      *)
  (* 把0-1-2 拷贝到 10-11-12 尾 = 2 + 10(长度)                     *)
  (* --------------------------------------------------------------*)

  if (m_TailPos < m_HeadPos) then
  begin
  (* 目的缓冲区位置：m_pNewBuffer+m_nPrevBufSize  *)
    CopyMemory(Pointer(LongInt(m_pNewBuffer) + m_nPrevBufSize), Pointer(LongInt(m_pBuffer)), m_TailPos);
  (* 修改尾位置                                   *)
    m_TailPos := m_TailPos + m_nPrevBufSize;
  end;
  (* 释放原缓冲区数据                             *)
  FreeMem(m_pBuffer, m_nPrevBufSize);
  m_pBuffer := m_pNewBuffer;
  {$IFDEF _ICOP_CIRCULARBUFFER}
  _GlobalLogger.AppendLogMessage(
    'BufferResize后环形Buffer数据: %S, 头位置: %d, 尾位置: %d, 缓冲区大小: %d.',
     [PChar(m_pBuffer)^, m_HeadPos, m_TailPos, m_nBufSize]);
  {$ENDIF}
end;

procedure TCircularBuffer.SetEmpty;
begin
  Lock;
  try
    m_HeadPos := 0;
    m_TailPos := 0;
  finally
    UnLock;
  end;
end;

function TCircularBuffer.GetSafeValidCount: Integer;
begin
  Lock;
  try
    Result := GetValidCount;
  finally
    UnLock;
  end;
end;

function TCircularBuffer.HeadSafePosInc(nStep: Integer): Boolean;
begin
  Lock;
  try
    Result := False;
    if (nStep <= GetValidCount) then
    begin
      m_HeadPos := m_HeadPos + nStep;
      m_HeadPos := m_HeadPos mod m_nBufSize;
      Result := m_HeadPos = m_TailPos;
    end;
  finally
    UnLock;
  end;
  {$IFDEF _ICOP_CIRCULARBUFFER}
   _GlobalLogger.AppendLogMessage(
    '头位置: %d, 尾位置: %d, 缓冲区大小: %d.',
    [m_HeadPos, m_TailPos, m_nBufSize]);
  {$ENDIF}
end;

procedure TCircularBuffer.WriteCirBuffer(pBuffer: PChar; m_nBufLen: Integer);
var
  m_nFirstCopySize: Integer;
  m_nSecondCopySize: Integer;
{$IFDEF _ICOP_CIRCULARBUFFER}
  sStr: String;
{$ENDIF}
begin
  if m_nBufLen <= 0 then Exit;
  Lock;
  try
    while IsOverFlowBuffer(m_nBufLen) do BufferResize;
    if IsIndexOverFlow(m_nBufLen) then
    begin
      (* 尾部需要拷贝的长度     *)
      m_nFirstCopySize := m_nBufSize - m_TailPos;
      (* 头部需要拷贝的长度     *)
      m_nSecondCopySize := m_nBufLen - m_nFirstCopySize;
      (* 拷贝到尾               *)
      CopyMemory(Pointer(LongInt(m_pBuffer) + m_TailPos), Pointer(LongInt(pBuffer)), m_nFirstCopySize);
      (* 拷贝到头               *)
      if (m_nSecondCopySize > 0) then
      begin
        CopyMemory(Pointer(LongInt(m_pBuffer)), Pointer(LongInt(pBuffer) + m_nFirstCopySize), m_nSecondCopySize);
        m_TailPos := m_nSecondCopySize;
      end
      else
        m_TailPos := 0;
    end
    else
    begin
       (* 如果未溢出，直接把数据附加到尾部 *)
      CopyMemory(Pointer(LongInt(m_pBuffer) + m_TailPos), Pointer(LongInt(pBuffer)), m_nBufLen);
      m_TailPos := m_TailPos + m_nBufLen;
    end;
  finally
    UnLock;
  end;
  {$IFDEF _ICOP_DEBUG}
  if m_nBufSize > 4096*2 then
    _GlobalLogger.AppendLogMessage( '环形缓冲区大小: %d.',[ m_nBufSize] );
  {$ENDIF}
  {$IFDEF _ICOP_CIRCULARBUFFER}
  sStr := pBuffer;
  _GlobalLogger.AppendLogMessage(
    '环形Buffer数据: %s, 写入数据: %s, 头位置: %d, 尾位置: %d, 缓冲区大小: %d.',
    [ PChar(m_pBuffer)^, sStr, m_HeadPos, m_TailPos, m_nBufSize]);
  {$ENDIF}
end;

procedure TCircularBuffer.ReadCirBuffer(pBuffer: PChar; m_nBufLen: Integer);
var
  m_dCirHeadPos: Integer;
  m_nFirstCopySize: Integer;
  m_nSecondCopySize: Integer;
begin
  if not ((m_nBufLen > 0) and (m_nBufLen <= GetSafeValidCount)) then Exit;
  Lock;
  try
    (* 得到偏移位置                           *)
    m_dCirHeadPos := m_HeadPos;
    (* 如果溢出, 偏移地址已经变化             *)
    if m_dCirHeadPos > m_nBufSize then
      m_dCirHeadPos := m_dCirHeadPos - m_nBufSize;

    (* --------------------------  说明  -------------------------*)
    (* 1 2 3 4 5  m_nBufSize = 5  m_HeadPos = 3  m_nBufLen =1时   *)
    (* 即长度在m_HeadPos之内的,可一次拷贝完整                     *)
    (* 如果长度在(m_nBufSize - m_HeadPos)                         *)
    (* -----------------------------------------------------------*)
    if (m_nBufLen < m_nBufSize - m_dCirHeadPos) then
      CopyMemory(Pointer(LongInt(pBuffer)), Pointer(LongInt(m_pBuffer) + m_dCirHeadPos), m_nBufLen)
    else
    begin
      (* 1 2 3 4 5  m_nBufSize = 5  m_HeadPos = 3  m_nBufLen =4时 *)
      m_nFirstCopySize := m_nBufSize - m_dCirHeadPos;
      m_nSecondCopySize := m_nBufLen - m_nFirstCopySize;
      CopyMemory(Pointer(LongInt(pBuffer)), Pointer(LongInt(m_pBuffer) + m_dCirHeadPos), m_nFirstCopySize);
      if (m_nSecondCopySize>0) then
        CopyMemory(Pointer(LongInt(pBuffer)+m_nFirstCopySize), Pointer(LongInt(m_pBuffer)), m_nSecondCopySize);
    end;
  finally
    UnLock;
  end;
end;

procedure TCircularBuffer.ReadCirBuffer(pBuffer: PChar; m_nBufLen: Integer; nPos: Integer);
var
  m_dCirHeadPos: Integer;
  m_nFirstCopySize: Integer;
  m_nSecondCopySize: Integer;
begin
  if not ((m_nBufLen > 0) and (m_nBufLen <= GetSafeValidCount - nPos)) then Exit;
  try
    Lock;
    (* 得到偏移位置                   *)
    m_dCirHeadPos := m_HeadPos + nPos;
    (* 如果溢出, 偏移地址已经变化     *)
    if m_dCirHeadPos > m_nBufSize then
      m_dCirHeadPos := m_dCirHeadPos - m_nBufSize;
    (* --------------------------  说明  -------------------------*)
    (* 1 2 3 4 5  m_nBufSize = 5  m_HeadPos = 3  m_nBufLen =1时   *)
    (* 即长度在m_HeadPos之内的,可一次拷贝完整                     *)
    (* 如果长度在(m_nBufSize - m_HeadPos)                         *)
    (* -----------------------------------------------------------*)
    if (m_nBufLen < m_nBufSize - (m_dCirHeadPos)) then
      CopyMemory(Pointer(LongInt(pBuffer)), Pointer(LongInt(m_pBuffer) + m_dCirHeadPos), m_nBufLen)
    else
    begin
      (* 1 2 3 4 5  m_nBufSize = 5  m_HeadPos = 3  m_nBufLen =4时 *)
      m_nFirstCopySize := m_nBufSize - m_dCirHeadPos;
      m_nSecondCopySize := m_nBufLen - m_nFirstCopySize;
      CopyMemory(Pointer(LongInt(pBuffer)), Pointer(LongInt(m_pBuffer) + m_dCirHeadPos), m_nFirstCopySize );
      if (m_nSecondCopySize > 0) then
        CopyMemory(Pointer(LongInt(pBuffer) + m_nFirstCopySize), Pointer(LongInt(m_pBuffer)), m_nSecondCopySize );
    end;
  finally
    UnLock;
  end;
end;

end.





